#include <sys/mman.h>
#include <fcntl.h>
#include <stdint.h>
#include <unistd.h>
#include <stdio.h>
#include <stdbool.h>
#include <sys/time.h>
#include <string.h>

#include "base_api.h"

void *gBaseAddr = NULL;

static DIN_t DINArray[TOTAL_CHANNEL][ONE_CHANNEL_ROW];

void assert_failed(uint8_t* file, uint32_t line)
{
    /* User can add his own implementation to report the file name and line number,
        ex: printf("Wrong parameters value: file %s on line %d\r\n", file, line) */

    printf("Wrong parameters value: file %s on line %d\r\n", file, (int)line);
    /* Infinite loop */
    while (1)
    {
    }
}

uint32_t ReadReg(uint32_t OffsetAddr)
{
    return *(volatile uint32_t *)(gBaseAddr + OffsetAddr);
}

void WriteReg(uint32_t OffsetAddr, uint32_t Value)
{
    volatile uint32_t *LocalAddr = (volatile uint32_t *)(gBaseAddr + OffsetAddr);
    *LocalAddr = Value;
}

void SetBit(uint32_t OffsetAddr, uint32_t shift)
{
    volatile uint32_t *LocalAddr = (volatile uint32_t *)(gBaseAddr + OffsetAddr);
    *LocalAddr |= ((uint32_t)1 << shift);
}

int WaitFlagBit(uint32_t OffsetAddr, uint32_t BitPosition, uint32_t delay, bool bBitSet)
{
    while (delay > 0)
    {
        uint32_t ret = ReadReg(OffsetAddr);
        if (bBitSet == true)
        {
            if ((ret & (1 << BitPosition)) != 0)
                return 0;
        }
        else
        {
            if ((ret & (1 << BitPosition)) == 0)
                return 0;
        }
        delay--;
        // usleep(1);
    }
    return -1;
}

int OpenMMIO(void)
{
    DEBUG_LINE();
    int ret = 0;
    int fd;

    fd = open("/dev/mem", O_RDWR | O_SYNC);
    if (fd == -1)
    {
        printf("failed to open /dev/mem!");
        return -1;
    }

    gBaseAddr = mmap(NULL, ADDRESS_RANGE, PROT_READ | PROT_WRITE, MAP_SHARED, fd, BASE_ADDR);
    if (gBaseAddr == MAP_FAILED)
    {
        printf("failed to mmap /dev/mem!");
        return -1;
    }
    return 0;
}

int CloseMMIO(void)
{
    DEBUG_LINE();
    int ret = 0;
    if (munmap(gBaseAddr, ADDRESS_RANGE) == -1)
    {
        printf("failed to munmap /dev/mem!");
        ret = -1;
    }
    return ret;
}

int WaitOpFinish(uint32_t op)
{
    uint32_t bitPosition = 0;
    if ((op == 0xf) || (op == 0x1f))
        bitPosition = 8;
    else
        bitPosition = op;

    // struct timeval tpstart, tpend;
    // gettimeofday(&tpstart, NULL);

    int ret = WaitFlagBit(REG0_ADDR, bitPosition, 1000000, true);

    // gettimeofday(&tpend, NULL);
    // uint32_t timeuse = (tpend.tv_usec - tpstart.tv_usec) + 1000000*(tpend.tv_sec - tpstart.tv_sec);
    // printf("timeuse = %d \n", timeuse);

    if (ret != 0)
        printf("The completion flag is not set");
    return ret;
}

int ClearOp(uint32_t op)
{
    uint32_t bitPosition = 0;
    uint32_t v = 0;
    if ((op == 0xf) || (op == 0x1f))
    {
        v = 0xFF;
        bitPosition = 8;
    }
    else
    {
        v = op | 0x10;
        bitPosition = op;
    }
    WriteReg(REG3_ADDR, v);

    // struct timeval tpstart, tpend;
    // gettimeofday(&tpstart, NULL);

    int ret = WaitFlagBit(REG0_ADDR, bitPosition, 1000000, false);

    // gettimeofday(&tpend, NULL);
    // uint32_t timeuse = (tpend.tv_usec - tpstart.tv_usec) + 1000000*(tpend.tv_sec - tpstart.tv_sec);
    // printf("timeuse = %d \n", timeuse);
    if (ret != 0)
        printf("The completion flag is not reset");
    return ret;
}

int OpFlow(uint32_t op)
{
    WriteReg(REG3_ADDR, op);
    WriteReg(REG1_ADDR, 1);

    // struct timeval tpstart, tpend;
    // gettimeofday(&tpstart, NULL);

    int ret = WaitOpFinish(op);

    // gettimeofday(&tpend, NULL);
    // uint32_t timeuse = (tpend.tv_usec - tpstart.tv_usec) + 1000000*(tpend.tv_sec - tpstart.tv_sec);
    // printf("timeuse = %d \n", timeuse);

    if (ret != 0)
        return -1;

    ret = ClearOp(op);

    if (ret != 0)
        return -1;
    WriteReg(REG1_ADDR, 0);
    return 0;
}

int PowerOn(void)
{
    return OpFlow(OP_POWER_ON);
}

int PowerOff(void)
{
    return OpFlow(OP_POWER_OFF);
}

#if 0
uint32_t GetAdcValue(uint32_t regVal, uint32_t colIdx)
{
    uint32_t idx = colIdx % 8;
    return (regVal >> ((7 - idx) * 4)) & 0xF;
}

void SelectOneCol(uint32_t colIdx)
{
    assert_param(colIdx < TOTAL_COL);
    WriteReg(REG52_ADDR + 4 * 0, 0);
    WriteReg(REG52_ADDR + 4 * 1, 0);
    WriteReg(REG52_ADDR + 4 * 2, 0);
    WriteReg(REG52_ADDR + 4 * 3, 0);
    uint32_t regNum = colIdx / 32;
    uint32_t remainder = colIdx % 32;
    uint32_t regVal = 1 << remainder;
    WriteReg(REG52_ADDR + 4 * regNum, regVal);
}
#else
uint32_t GetAdcValue(uint32_t regVal, uint32_t colIdx)
{
    uint32_t idx = colIdx % 8;
    return (regVal >> (idx * 4)) & 0xF;
}

void SelectOneCol(uint32_t colIdx)
{
    assert_param(colIdx < TOTAL_COL);
    WriteReg(REG52_ADDR + 4 * 0, 0);
    WriteReg(REG52_ADDR + 4 * 1, 0);
    WriteReg(REG52_ADDR + 4 * 2, 0);
    WriteReg(REG52_ADDR + 4 * 3, 0);
    uint32_t regNum = colIdx / 32;
    uint32_t remainder = colIdx % 32;
    uint32_t regVal = 1 << (31 - remainder);
    WriteReg(REG55_ADDR - 4 * regNum, regVal);
}
#endif

void SelectOneRow(uint32_t rowIdx, uint32_t PNdir)
{
    assert_param((PNdir > 0) && (PNdir < 3));
    uint32_t regRow = 0;
    uint32_t channel = rowIdx * 2 / ONE_CHANNEL_ROW;
    if (PNdir == POS_DIR)
    {
        regRow = rowIdx * 2 % ONE_CHANNEL_ROW + 1;
        WriteReg(REG14_ADDR, channel);
    }
    else
    {
        regRow = rowIdx * 2 % ONE_CHANNEL_ROW + 2;
        WriteReg(REG14_ADDR, channel | 0x80000000);
    }
    WriteReg(REG13_ADDR, regRow);
}

uint32_t DACVToReg(float voltage)
{
    return (uint32_t) (voltage * 0xffff / 5);
}

int CfgDAC(uint32_t channel, float value)
{
    assert_param((0 <= channel) && (channel <= 15));
    uint32_t RegVal = DACVToReg(value);
    WriteReg(REG5_ADDR, RegVal);
    RegVal = (1 << 4) | channel;
    WriteReg(REG4_ADDR, RegVal);
    return OpFlow(OP_CFG_DAC);
}

int CfgVBLMid(float value)
{
    return CfgDAC(5, value);
}

int CfgVBLNeg(float value)
{
    return CfgDAC(15, value);
}

int CfgVBLPos(float value)
{
    return CfgDAC(14, value);
}

int CfgVrefCompDn(float value)
{
    return CfgDAC(7, value);
}

int CfgVrefCompUp(float value)
{
    return CfgDAC(4, value);
}

int CfgVSLClamp(float value)
{
    return CfgDAC(0, value);
}

int CfgVDDLV(float value)
{
    return CfgDAC(12, value);
}

int CfgFormVBL(float value)
{
    return CfgDAC(6, value);
}

int CfgFormVWL(float value)
{
    return CfgDAC(1, value);
}

int CfgSetVBL(float value)
{
    return CfgDAC(13, value);
}

int CfgSetVWL(float value)
{
    return CfgDAC(3, value);
}

int CfgResetVSL(float value)
{
    return CfgDAC(2, value);
}

int CfgResetVWL(float value)
{
    return CfgDAC(11, value);
}

void CfgFloatBL(void)
{
    WriteReg(REG12_ADDR, 1 | (1 << 4));
}

void CfgClockMhz(uint32_t clockMhz)
{
    WriteReg(REG10_ADDR, 100 / clockMhz);
}

void CfgFRSPulse(uint32_t ts, uint32_t tp, uint32_t te)
{
    WriteReg(REG7_ADDR, tp | (ts << 8));
    WriteReg(REG56_ADDR, te);
}

void CfgReadPulse(uint32_t ti, uint32_t ts, uint32_t tb, uint32_t te)
{
    uint32_t regVal = tb | (ts << 8) | (ti << 16);
    WriteReg(REG6_ADDR, regVal);
    WriteReg(REG56_ADDR, te);
}

void ResetSystem(void)
{
    WriteReg(REG2_ADDR, 1);
    sleep(0.01);
    WriteReg(REG2_ADDR, 0);
}

void DevInit(void)
{
    ResetSystem();
    CfgFloatBL();
    CfgClockMhz(10);
    CfgFRSPulse(0x50, 0x3C, 0x3C);
    CfgReadPulse(0x20, 0x28, 0xFF, 0x3C);
    PowerOn();
    CfgVBLMid(2.5);
    CfgVBLNeg(2.598);
    CfgVBLPos(2.399);
    CfgVrefCompDn(0);
    CfgVrefCompUp(4.699);
    CfgVSLClamp(2.5);
    CfgVDDLV(1.799);
    CfgSetVBL(2.814);
    CfgSetVWL(2.01);
    CfgResetVSL(4.298);
    CfgResetVWL(3.968);
    CfgFormVBL(4.556);
    CfgFormVWL(1.871);
}

int SelectChip(uint32_t ChipNum)
{
    assert_param(ChipNum < 12);
    ChipNum += 1;
    WriteReg(REG67_ADDR, ChipNum);
    int ret = OpFlow(OP_SELECT_CHIP);
    usleep(10000);
    return ret;
}

int OneCellOp(uint32_t op, uint32_t rowIdx, uint32_t colIdx, uint32_t PNdir)
{
    SelectOneRow(rowIdx, PNdir);
    SelectOneCol(colIdx);

    if (op == OP_CALC)
    {
        WriteReg(REG11_ADDR, 8 | POS_DIR | NEG_DIR);
    }
    else
    {
        if (PNdir == POS_DIR)
            WriteReg(REG11_ADDR, 8 | POS_DIR);
        else
            WriteReg(REG11_ADDR, 8 | NEG_DIR);
    }
    if (op == OP_RESET)
        WriteReg(REG15_ADDR, 0x80000001);
    else
        WriteReg(REG15_ADDR, 1);
    return OpFlow(op);
}

uint32_t ReadOneCell(uint32_t rowIdx, uint32_t colIdx, uint32_t PNdir)
{
    uint32_t value = 0;
    OneCellOp(OP_READ, rowIdx, colIdx, PNdir);
    if (PNdir == POS_DIR)
        value = ReadReg(REG76_ADDR);
    else
        value = ReadReg(REG77_ADDR);

    return GetAdcValue(value, colIdx);
}

uint32_t ReadOneCell_1(uint32_t rowIdx, uint32_t colIdx)
{
    uint32_t row = rowIdx / 2;
    uint32_t PNdir = POS_DIR;
    if (rowIdx % 2 == 0)
        PNdir = NEG_DIR;
    else
        PNdir = POS_DIR;
    return ReadOneCell(row, colIdx, PNdir);
}

void CfgPara(DIN_t *din, uint32_t regAddr, uint32_t selectedBitMap, uint32_t actualBitMap)
{
    din->regAddr = regAddr;
    din->selectedBitMap = selectedBitMap;
    din->actualBitMap = actualBitMap;
}

void CfgDIN(void)
{
    uint32_t reg = REG51_ADDR;
    for(uint32_t i=0; i<(TOTAL_ROW / 32); i++)
    {
        CfgPara(&DINArray[0][0 + 4 * i], reg, 0x01000000, 0);
        CfgPara(&DINArray[0][1 + 4 * i], reg, 0x00010000, 0);
        CfgPara(&DINArray[0][2 + 4 * i], reg, 0x00000100, 0);
        CfgPara(&DINArray[0][3 + 4 * i], reg, 0x00000001, 0);

        CfgPara(&DINArray[1][0 + 4 * i], reg, 0x02000000, 0);
        CfgPara(&DINArray[1][1 + 4 * i], reg, 0x00020000, 0);
        CfgPara(&DINArray[1][2 + 4 * i], reg, 0x00000200, 0);
        CfgPara(&DINArray[1][3 + 4 * i], reg, 0x00000002, 0);

        CfgPara(&DINArray[2][0 + 4 * i], reg, 0x04000000, 0);
        CfgPara(&DINArray[2][1 + 4 * i], reg, 0x00040000, 0);
        CfgPara(&DINArray[2][2 + 4 * i], reg, 0x00000400, 0);
        CfgPara(&DINArray[2][3 + 4 * i], reg, 0x00000004, 0);

        CfgPara(&DINArray[3][0 + 4 * i], reg, 0x08000000, 0);
        CfgPara(&DINArray[3][1 + 4 * i], reg, 0x00080000, 0);
        CfgPara(&DINArray[3][2 + 4 * i], reg, 0x00000800, 0);
        CfgPara(&DINArray[3][3 + 4 * i], reg, 0x00000008, 0);

        CfgPara(&DINArray[4][0 + 4 * i], reg, 0x10000000, 0);
        CfgPara(&DINArray[4][1 + 4 * i], reg, 0x00100000, 0);
        CfgPara(&DINArray[4][2 + 4 * i], reg, 0x00001000, 0);
        CfgPara(&DINArray[4][3 + 4 * i], reg, 0x00000010, 0);

        CfgPara(&DINArray[5][0 + 4 * i], reg, 0x20000000, 0);
        CfgPara(&DINArray[5][1 + 4 * i], reg, 0x00200000, 0);
        CfgPara(&DINArray[5][2 + 4 * i], reg, 0x00002000, 0);
        CfgPara(&DINArray[5][3 + 4 * i], reg, 0x00000020, 0);

        CfgPara(&DINArray[6][0 + 4 * i], reg, 0x40000000, 0);
        CfgPara(&DINArray[6][1 + 4 * i], reg, 0x00400000, 0);
        CfgPara(&DINArray[6][2 + 4 * i], reg, 0x00004000, 0);
        CfgPara(&DINArray[6][3 + 4 * i], reg, 0x00000040, 0);

        CfgPara(&DINArray[7][0 + 4 * i], reg, 0x80000000, 0);
        CfgPara(&DINArray[7][1 + 4 * i], reg, 0x00800000, 0);
        CfgPara(&DINArray[7][2 + 4 * i], reg, 0x00008000, 0);
        CfgPara(&DINArray[7][3 + 4 * i], reg, 0x00000080, 0);
        reg = reg - 4;
    }
}

int SelectInput(int8_t *inputData)
{
    uint32_t rowIndex = 0;
    uint32_t channel = 0;
    uint32_t num = 0;
    int8_t data = 0;
    for (uint32_t idx=0; idx < (TOTAL_ROW / 2); idx++)
    {
        data = inputData[idx];
        rowIndex = idx * 2;
        channel = rowIndex / 144;
        num = rowIndex % 144;
        if (data == -1)
        {
            DINArray[channel][num].actualBitMap = DINArray[channel][num].selectedBitMap;
            DINArray[channel][num + 1].actualBitMap = 0;
        }
        else if (data == 1)
        {
            DINArray[channel][num].actualBitMap = 0;
            DINArray[channel][num + 1].actualBitMap = DINArray[channel][num + 1].selectedBitMap;
        }
        else if (data == 0)
        {
            DINArray[channel][num].actualBitMap = 0;
            DINArray[channel][num + 1].actualBitMap = 0;
        }
        else
        {
            printf("The element of inputData must be -1, 0, 1 \n");
            return -1;
        }
    }

    for (uint32_t i=0; i < (TOTAL_ROW / 32); i++)
    {
        uint32_t v = 0;
        for (uint32_t j=0; j< TOTAL_CHANNEL; j++)
        {
            v = v | DINArray[j][0 + 4 * i].actualBitMap;
            v = v | DINArray[j][1 + 4 * i].actualBitMap;
            v = v | DINArray[j][2 + 4 * i].actualBitMap;
            v = v | DINArray[j][3 + 4 * i].actualBitMap;
        }
        WriteReg(DINArray[0][0 + 4 * i].regAddr, v);
    }
    return 0;
}

void CalcArray(int8_t *rowInput, uint32_t colStart, uint32_t colCount, uint8_t *output)
{
    DEBUG_INFO("rowInput %d, %d", rowInput[0], rowInput[1]);
    DEBUG_INFO("colStart=%d, colCount=%d", colStart, colCount);

    WriteReg(REG11_ADDR, 8 | POS_DIR | NEG_DIR);
    WriteReg(REG15_ADDR, 0);

    // struct timeval tpstart, tpend;
    // gettimeofday(&tpstart, NULL);

    SelectInput(rowInput);

    // gettimeofday(&tpend, NULL);
    // uint32_t timeuse = (tpend.tv_usec - tpstart.tv_usec) + 1000000*(tpend.tv_sec - tpstart.tv_sec);
    // printf("timeuse = %d \n", timeuse);

    uint8_t *ptr = output;
    for (uint32_t colIndex=colStart; colIndex<colStart + colCount; colIndex++)
    {
        SelectOneCol(colIndex);
        OpFlow(OP_CALC);
        uint32_t value = ReadReg(REG76_ADDR);
        *ptr = (uint8_t)GetAdcValue(value, colIndex);
        ptr++;
    }
    DEBUG_INFO("output %d, %d", output[0], output[1]);
}

void ClearRowRegs(void)
{
    void *RowRegAddr = gBaseAddr + REG16_ADDR;
    memset(RowRegAddr, 0, 144);
}

#if 0

int SelectInputNew(int8_t *inputData, uint32_t rowStart, uint32_t rowCount)
{
    DEBUG_INFO("rowStart=%d, rowCount=%d", rowStart, rowCount);
    ClearRowRegs();
    for (uint32_t idx=rowStart; idx < rowStart + rowCount; idx++)
    {
        int8_t data = inputData[idx-rowStart];
        DEBUG_INFO("data=%d", data);
        if (data == 1)
        {
            uint32_t regaddr = REG51_ADDR - (((idx % 72) >> 1) << 2);
            uint32_t bitaddr;
            if ((idx & 1) == 0)
            {
                bitaddr = idx / 72 + 16;
            }
            else
            {
                bitaddr = idx / 72;
            }
            DEBUG_INFO("regaddr=%d, bitaddr=%d", regaddr, bitaddr);
            SetBit(regaddr, bitaddr);
        }
        else if (data == 0)
        {
            continue;
        }
        else
        {
            printf("The element of inputData must be 0, 1 \n");
            return -1;
        }
    }

    return 0;
}

#else

int SelectInputNew(int8_t *inputData, uint32_t rowStart, uint32_t rowCount)
{
    DEBUG_INFO("rowStart=%d, rowCount=%d", rowStart, rowCount);
    ClearRowRegs();
    for (uint32_t idx=rowStart; idx < rowStart + rowCount; idx++)
    {
        int8_t data = inputData[idx-rowStart];
        DEBUG_INFO("data=%d", data);
        if (data == 1)
        {
            uint32_t regaddr = REG51_ADDR - (((idx % 72) >> 1) << 2);
            uint32_t bitaddr;
            if ((idx & 1) == 0)
            {
                bitaddr = idx / 72 + 16;
            }
            else
            {
                bitaddr = idx / 72;
            }
            DEBUG_INFO("regaddr=%d, bitaddr=%d", regaddr, bitaddr);
            SetBit(regaddr, bitaddr);
        }
        else if (data == -1)
        {
            uint32_t regaddr = REG51_ADDR - (((idx % 72) >> 1) << 2);
            uint32_t bitaddr;
            if ((idx & 1) == 0)
            {
                bitaddr = idx / 72 + 24;
            }
            else
            {
                bitaddr = idx / 72 + 8;
            }
            DEBUG_INFO("regaddr=%d, bitaddr=%d", regaddr, bitaddr);
            SetBit(regaddr, bitaddr);
        }
        else if (data == 0)
        {
            continue;
        }
        else
        {
            printf("The element of inputData must be -1, 0, 1 \n");
            return -1;
        }
    }

    return 0;
}

#endif

int CalcArray_1(int8_t *rowInput, uint32_t rowStart, uint32_t rowCount, uint32_t colStart, uint32_t colCount, uint8_t *output)
{
    // struct timeval tpstart, tpend;
    // gettimeofday(&tpstart, NULL);

    WriteReg(REG11_ADDR, 8 | POS_DIR | NEG_DIR);
    WriteReg(REG15_ADDR, 0);

    int ret = SelectInputNew(rowInput, rowStart, rowCount);
    if (ret != 0)
        return -1;

    uint8_t *ptr = output;
    for (uint32_t colIndex=colStart; colIndex<colStart + colCount; colIndex++)
    {
        if (colIndex == colStart)
            SelectOneCol(colIndex);

        if (colIndex == (colStart + 1))
        {
            WriteReg(REG75_ADDR, 1);
        }

        ret = OpFlow(OP_CALC);
        if (ret != 0)
            break;

        uint32_t value = ReadReg(REG76_ADDR);
        *ptr = (uint8_t)GetAdcValue(value, colIndex);
        ptr++;
    }
    WriteReg(REG75_ADDR, 0);

    // gettimeofday(&tpend, NULL);
    // uint32_t timeuse = (tpend.tv_usec - tpstart.tv_usec) + 1000000*(tpend.tv_sec - tpstart.tv_sec);
    // printf("timeuse = %d \n", timeuse);
    return ret;
}

int CalcArray_2(int8_t *rowInput, uint32_t rowStart, uint32_t rowCount, uint32_t colStart, uint32_t colCount, uint8_t *output, uint32_t calcCount)
{
    int8_t *pRowInput = rowInput;
    uint8_t *pOutput = output;
    int ret = 0;
    // struct timeval tpstart, tpend;
    // gettimeofday(&tpstart, NULL);
    for (uint32_t idx=0; idx<calcCount; idx++)
    {
        ret = CalcArray_1(pRowInput, rowStart, rowCount, colStart, colCount, pOutput);
        if (ret != 0)
            break;
        pRowInput += rowCount;
        pOutput += colCount;
    }
    // gettimeofday(&tpend, NULL);
    // uint32_t timeuse = (tpend.tv_usec - tpstart.tv_usec) + 1000000 * (tpend.tv_sec - tpstart.tv_sec);
    // printf("timeuse = %d \n", timeuse);
    return ret;
}
